/***************************************************************************
 *   Copyright (C) 2012 by Mathias Paulin                                  *
 *   Mathias.Paulin@irit.fr                                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "objloader.h"

#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <utility>
#include <QFileInfo>
#include "Material.h"

namespace loaders {
namespace obj_mtl {

ObjLoader::ObjLoader() {
    vertices = 0;
    normals = 0;
    textures = 0;
    materialNumber = 0;
    currentGroup = new Group("default");
    allgroups["default"] = currentGroup;
    groupsNumber = 1;
    verticesTable.reserve(100000);
    normalsTable.reserve(100000);
    texturesTable.reserve(100000);

}

ObjLoader::~ObjLoader() {
    mtllib.erase(mtllib.begin(), mtllib.end());
    //     delete allgroups["default"];
    allgroups.erase(allgroups.begin(), allgroups.end());
}

void ObjLoader::info_callback(const std::string& filename,
                              std::size_t /*line_number*/, const std::string& message) {
    std::ostringstream info_message;
    info_message << filename << std::endl << message << std::endl;
    lastParseMessage = info_message.str();
}

void ObjLoader::warning_callback(const std::string& filename,
                                 std::size_t line_number, const std::string& message) {
    std::cerr << filename << ":" << line_number << ": " << "warning: "
              << message << std::endl;
}

void ObjLoader::error_callback(const std::string& /*filename*/,
                               std::size_t line_number, const std::string& message) {
    std::ostringstream info_message;
    info_message << "Error at line " << line_number << std::endl << message
                 << std::endl;
    lastParseMessage = info_message.str();
}

bool ObjLoader::load(const QString & filename, QString &reason) {
    obj_mtl::obj_parser *parser =
            new obj_mtl::obj_parser(
                obj_mtl::obj_parser::translate_negative_indices/*obj_mtl::obj_parser::triangulate_faces*/);

    std::ifstream file(filename.toStdString().c_str());

    /* Association des callbacks */
    parser->info_callback(
                std::tr1::bind(&ObjLoader::info_callback, this,
                               filename.toStdString(), std::tr1::placeholders::_1,
                               std::tr1::placeholders::_2));
    parser->warning_callback(
                std::tr1::bind(&ObjLoader::warning_callback, this,
                               filename.toStdString(), std::tr1::placeholders::_1,
                               std::tr1::placeholders::_2));
    parser->error_callback(
                std::tr1::bind(&ObjLoader::error_callback, this,
                               filename.toStdString(), std::tr1::placeholders::_1,
                               std::tr1::placeholders::_2));

    parser->geometric_vertex_callback(
                std::tr1::bind(&ObjLoader::vertex_callback, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    parser->vertex_normal_callback(
                std::tr1::bind(&ObjLoader::normal_callback, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    parser->texture_vertex_callback(
                std::tr1::bind(&ObjLoader::texture_callback, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2));

    parser->face_callbacks(
                //triangular_face_geometric_vertices_callback
                std::tr1::bind(&ObjLoader::add_face_T_vertices, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3),
                //triangular_face_geometric_vertices_texture_vertices_callback
                std::tr1::bind(&ObjLoader::add_face_T_vertices_textures, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3),
                //triangular_face_geometric_vertices_vertex_normals_callback
                std::tr1::bind(&ObjLoader::add_face_T_vertices_normals, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3),
                //triangular_face_geometric_vertices_texture_vertices_vertex_normals_callback
                std::tr1::bind(&ObjLoader::add_face_T_vertices_textures_normals,
                               this, std::tr1::placeholders::_1,
                               std::tr1::placeholders::_2, std::tr1::placeholders::_3),
                //quadrilateral_face_geometric_vertices_callback
                std::tr1::bind(&ObjLoader::add_face_Q_vertices, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3, std::tr1::placeholders::_4),
                //quadrilateral_face_geometric_vertices_texture_vertices_callback
                std::tr1::bind(&ObjLoader::add_face_Q_vertices_textures, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3, std::tr1::placeholders::_4),
                //quadrilateral_face_geometric_vertices_vertex_normals_callback
                std::tr1::bind(&ObjLoader::add_face_Q_vertices_normals, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3, std::tr1::placeholders::_4),
                //quadrilateral_face_geometric_vertices_texture_vertices_vertex_normals_callback
                std::tr1::bind(&ObjLoader::add_face_Q_vertices_textures_normals,
                               this, std::tr1::placeholders::_1,
                               std::tr1::placeholders::_2, std::tr1::placeholders::_3,
                               std::tr1::placeholders::_4));

    parser->group_name_callback(
                std::tr1::bind(&ObjLoader::set_group, this,
                               std::tr1::placeholders::_1));
    parser->smoothing_group_callback(
                std::tr1::bind(&ObjLoader::smooth_group, this,
                               std::tr1::placeholders::_1));
    parser->object_name_callback(
                std::tr1::bind(&ObjLoader::set_group, this,
                               std::tr1::placeholders::_1));
    parser->material_name_callback(
                std::tr1::bind(&ObjLoader::set_material, this,
                               std::tr1::placeholders::_1));

    QString dirname = QFileInfo(filename).absolutePath() + "/";

    mObjDir = dirname;
    parser->material_library_callback(
                std::tr1::bind(&ObjLoader::parse_material_library, this,
                               dirname.toStdString(), std::tr1::placeholders::_1));

    /* Parse */
    bool result = parser->parse(file);
    std::cerr << lastParseMessage;
    reason = QString(lastParseMessage.c_str());

    delete parser;
    return result;
}

void ObjLoader::parse_material_library(const std::string &dirname,
                                       const std::string &name) {

    std::string filename;
    filename = dirname + name;
    std::ifstream file(filename.c_str());

    //     std::cerr << "parse_material_library " << filename << std::endl;

    obj_mtl::mtl_parser *mtlparser = new obj_mtl::mtl_parser();

    /* Association des callbacks */
    mtlparser->info_callback(
                std::tr1::bind(&ObjLoader::info_callback, this, filename,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2));
    mtlparser->warning_callback(
                std::tr1::bind(&ObjLoader::warning_callback, this, filename,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2));
    mtlparser->error_callback(
                std::tr1::bind(&ObjLoader::error_callback, this, filename,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2));

    mtlparser->material_name_callback(
                std::tr1::bind(&ObjLoader::new_material, this,
                               std::tr1::placeholders::_1));

    mtlparser->material_Ka_callback(
                std::tr1::bind(&ObjLoader::material_Ka, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    mtlparser->material_Kd_callback(
                std::tr1::bind(&ObjLoader::material_Kd, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    mtlparser->material_Ks_callback(
                std::tr1::bind(&ObjLoader::material_Ks, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    mtlparser->material_Tf_callback(
                std::tr1::bind(&ObjLoader::material_Tf, this,
                               std::tr1::placeholders::_1, std::tr1::placeholders::_2,
                               std::tr1::placeholders::_3));
    mtlparser->material_shininess_callback(
                std::tr1::bind(&ObjLoader::material_shininess, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_map_Kd_callback(
                std::tr1::bind(&ObjLoader::material_map_Kd, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_map_Ks_callback(
                std::tr1::bind(&ObjLoader::material_map_Ks, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_map_Ns_callback(
                std::tr1::bind(&ObjLoader::material_map_Ns, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_map_d_callback(
                std::tr1::bind(&ObjLoader::material_map_d, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_bumpmap_callback(
                std::tr1::bind(&ObjLoader::material_bump, this,
                               std::tr1::placeholders::_1));
    mtlparser->material_normalmap_callback(
                std::tr1::bind(&ObjLoader::material_normal, this,
                               std::tr1::placeholders::_1));

    mtlparser->material_dissolve_callback(
                std::tr1::bind(&ObjLoader::material_dissolve, this,
                               std::tr1::placeholders::_1));

    /* Parse */
    mtlparser->parse(file);
    std::cerr << lastParseMessage;
    delete mtlparser;
}

int ObjLoader::faceType(Face *f) {
    int type = 4; // 0 -> full, 1 -> normales, 2 -> textures, 3 ->vertex uniquement
    if (f->have[NORMALS] && f->have[TEXTURES])
        type = 0;
    else if (f->have[NORMALS])
        type = 1;
    else if (f->have[TEXTURES])
        type = 2;
    else
        type = 3;
    return type;
}

void ObjLoader::addVerticeNormalTexturePart(ObjMesh *mesh, FaceList &faces,
                                            int num) {
    std::vector<Face *>::iterator it = faces.begin();
    std::map<VertexObj, int> vertexBuffer;
    // Etape 1 : construire la liste des sommets (vert.norm.tex) uniques
    int addedVerticesNumber = 0;
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0],
                     (*it)->normals[0]);
        vertexBuffer[v1] = ++addedVerticesNumber;
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1],
                     (*it)->normals[1]);
        vertexBuffer[v2] = ++addedVerticesNumber;
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2],
                     (*it)->normals[2]);
        vertexBuffer[v3] = ++addedVerticesNumber;
        if ((*it)->type == QUAD) {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3],
                         (*it)->normals[3]);
            vertexBuffer[v4] = ++addedVerticesNumber;
        }
    } while (++it != faces.end());

    // Etape 2 : numeroter les sommets
    int newnumber = 0;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm)
        vm->second = newnumber++;

    // Etape 3 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);

    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0],
                     (*it)->normals[0]);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1],
                     (*it)->normals[1]);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2],
                     (*it)->normals[2]);
        int index1 = vertexBuffer.find(v1)->second;
        int index2 = vertexBuffer.find(v2)->second;
        int index3 = vertexBuffer.find(v3)->second;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3],
                         (*it)->normals[3]);
            int index4 = vertexBuffer.find(v4)->second;
            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());

    // etape4 : construire le tableau de sommet final
    std::vector<float> glVertexBuffer;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm) {
        glVertexBuffer.push_back(vm->first.vertex.x);
        glVertexBuffer.push_back(vm->first.vertex.y);
        glVertexBuffer.push_back(vm->first.vertex.z);
        glVertexBuffer.push_back(vm->first.normal.x);
        glVertexBuffer.push_back(vm->first.normal.y);
        glVertexBuffer.push_back(vm->first.normal.z);
        glVertexBuffer.push_back(vm->first.texcoord[0]);
        glVertexBuffer.push_back(vm->first.texcoord[1]);
    }
    // etape 5 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, true, true);
    mesh->addSmoothGroup(theSmoothGroup);

    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
    vertexBuffer.erase(vertexBuffer.begin(), vertexBuffer.end());
}

void ObjLoader::addVerticeNormalPart(ObjMesh *mesh, FaceList &faces, int num) {
    std::vector<Face *>::iterator it = faces.begin();
    std::map<VertexObj, int> vertexBuffer;
    // Etape 1 : construire la liste des sommets (vert.norm.tex) uniques
    int addedVerticesNumber = 0;
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->normals[0]);
        vertexBuffer[v1] = ++addedVerticesNumber;
        VertexObj v2(*this, (*it)->vertices[1], (*it)->normals[1]);
        vertexBuffer[v2] = ++addedVerticesNumber;
        VertexObj v3(*this, (*it)->vertices[2], (*it)->normals[2]);
        vertexBuffer[v3] = ++addedVerticesNumber;
        if ((*it)->type == QUAD) {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->normals[3]);
            vertexBuffer[v4] = ++addedVerticesNumber;
        }
    } while (++it != faces.end());

    // Etape 2 : numeroter les sommets
    int newnumber = 0;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm)
        vm->second = newnumber++;

    // Etape 3 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);

    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->normals[0]);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->normals[1]);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->normals[2]);
        int index1 = vertexBuffer.find(v1)->second;
        int index2 = vertexBuffer.find(v2)->second;
        int index3 = vertexBuffer.find(v3)->second;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->normals[3]);
            int index4 = vertexBuffer.find(v4)->second;
            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());

    // etape4 : construire le tableau de sommet final
    std::vector<float> glVertexBuffer;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm) {
        glVertexBuffer.push_back(vm->first.vertex.x);
        glVertexBuffer.push_back(vm->first.vertex.y);
        glVertexBuffer.push_back(vm->first.vertex.z);
        glVertexBuffer.push_back(vm->first.normal.x);
        glVertexBuffer.push_back(vm->first.normal.y);
        glVertexBuffer.push_back(vm->first.normal.z);
    }
    // etape 5 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, true, false);
    mesh->addSmoothGroup(theSmoothGroup);

    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
    vertexBuffer.erase(vertexBuffer.begin(), vertexBuffer.end());
}

void ObjLoader::addVerticeTexturePart(ObjMesh *mesh, FaceList &faces, int num) {
    std::vector<Face *>::iterator it = faces.begin();
    std::map<VertexObj, int> vertexBuffer;
    // Etape 1 : construire la liste des sommets (vert.tex) uniques
    int addedVerticesNumber = 0;
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0], true);
        vertexBuffer[v1] = ++addedVerticesNumber;
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1], true);
        vertexBuffer[v2] = ++addedVerticesNumber;
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2], true);
        vertexBuffer[v3] = ++addedVerticesNumber;
        if ((*it)->type == QUAD) {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3], true);
            vertexBuffer[v4] = ++addedVerticesNumber;
        }
    } while (++it != faces.end());

    // Etape 2 : numeroter les sommets
    int newnumber = 0;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm)
        vm->second = newnumber++;

    // Etape 3 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);

    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0], true);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1], true);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2], true);
        int index1 = vertexBuffer.find(v1)->second;
        int index2 = vertexBuffer.find(v2)->second;
        int index3 = vertexBuffer.find(v3)->second;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3], true);
            int index4 = vertexBuffer.find(v4)->second;
            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());

    // etape4 : construire le tableau de sommet final
    std::vector<float> glVertexBuffer;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm) {
        glVertexBuffer.push_back(vm->first.vertex.x);
        glVertexBuffer.push_back(vm->first.vertex.y);
        glVertexBuffer.push_back(vm->first.vertex.z);
        glVertexBuffer.push_back(vm->first.texcoord[0]);
        glVertexBuffer.push_back(vm->first.texcoord[1]);
    }
    // etape 5 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, false, true);
    mesh->addSmoothGroup(theSmoothGroup);

    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
    vertexBuffer.erase(vertexBuffer.begin(), vertexBuffer.end());

}

void ObjLoader::addVerticePart(ObjMesh *mesh, FaceList &faces, int num) {
    std::vector<Face *>::iterator it = faces.begin();
    std::map<VertexObj, int> vertexBuffer;
    // Etape 1 : construire la liste des sommets (vert) uniques
    int addedVerticesNumber = 0;
    do {
        VertexObj v1(*this, (*it)->vertices[0]);
        vertexBuffer[v1] = ++addedVerticesNumber;
        VertexObj v2(*this, (*it)->vertices[1]);
        vertexBuffer[v2] = ++addedVerticesNumber;
        VertexObj v3(*this, (*it)->vertices[2]);
        vertexBuffer[v3] = ++addedVerticesNumber;

        if ((*it)->type == QUAD) {
            VertexObj v4(*this, (*it)->vertices[3]);
            vertexBuffer[v4] = ++addedVerticesNumber;

        }
    } while (++it != faces.end());

    // Etape 2 : numeroter les sommets
    int newnumber = 0;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm)
        vm->second = newnumber++;

    // Etape 3 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);

    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0]);
        VertexObj v2(*this, (*it)->vertices[1]);
        VertexObj v3(*this, (*it)->vertices[2]);
        int index1 = vertexBuffer.find(v1)->second;
        int index2 = vertexBuffer.find(v2)->second;
        int index3 = vertexBuffer.find(v3)->second;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3]);
            int index4 = vertexBuffer.find(v4)->second;
            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());

    // etape4 : construire le tableau de sommet final
    std::vector<float> glVertexBuffer;
    for (std::map<VertexObj, int>::iterator vm = vertexBuffer.begin();
         vm != vertexBuffer.end(); ++vm) {
        glVertexBuffer.push_back(vm->first.vertex.x);
        glVertexBuffer.push_back(vm->first.vertex.y);
        glVertexBuffer.push_back(vm->first.vertex.z);
    }
    // etape 5 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, false, false);
    mesh->addSmoothGroup(theSmoothGroup);

    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
    vertexBuffer.erase(vertexBuffer.begin(), vertexBuffer.end());
}

void ObjLoader::addRawVerticeNormalTexturePart(ObjMesh *mesh, FaceList &faces,
                                               int num) {
    std::vector<Face *>::iterator it = faces.begin();
    // Etape 1 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;
    std::vector<float> glVertexBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);
    glVertexBuffer.reserve(3. * reservedSize);

    int indexVertex = 0;
    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0],
                     (*it)->normals[0]);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1],
                     (*it)->normals[1]);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2],
                     (*it)->normals[2]);

        glVertexBuffer.push_back(v1.vertex.x);
        glVertexBuffer.push_back(v1.vertex.y);
        glVertexBuffer.push_back(v1.vertex.z);
        glVertexBuffer.push_back(v1.normal.x);
        glVertexBuffer.push_back(v1.normal.y);
        glVertexBuffer.push_back(v1.normal.z);
        glVertexBuffer.push_back(v1.texcoord[0]);
        glVertexBuffer.push_back(v1.texcoord[1]);

        glVertexBuffer.push_back(v2.vertex.x);
        glVertexBuffer.push_back(v2.vertex.y);
        glVertexBuffer.push_back(v2.vertex.z);
        glVertexBuffer.push_back(v2.normal.x);
        glVertexBuffer.push_back(v2.normal.y);
        glVertexBuffer.push_back(v2.normal.z);
        glVertexBuffer.push_back(v2.texcoord[0]);
        glVertexBuffer.push_back(v2.texcoord[1]);

        glVertexBuffer.push_back(v3.vertex.x);
        glVertexBuffer.push_back(v3.vertex.y);
        glVertexBuffer.push_back(v3.vertex.z);
        glVertexBuffer.push_back(v3.normal.x);
        glVertexBuffer.push_back(v3.normal.y);
        glVertexBuffer.push_back(v3.normal.z);
        glVertexBuffer.push_back(v3.texcoord[0]);
        glVertexBuffer.push_back(v3.texcoord[1]);

        int index1 = indexVertex++;
        int index2 = indexVertex++;
        int index3 = indexVertex++;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3],
                         (*it)->normals[3]);
            glVertexBuffer.push_back(v4.vertex.x);
            glVertexBuffer.push_back(v4.vertex.y);
            glVertexBuffer.push_back(v4.vertex.z);
            glVertexBuffer.push_back(v4.normal.x);
            glVertexBuffer.push_back(v4.normal.y);
            glVertexBuffer.push_back(v4.normal.z);
            glVertexBuffer.push_back(v4.texcoord[0]);
            glVertexBuffer.push_back(v4.texcoord[1]);

            int index4 = indexVertex++;

            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());
    // etape 2 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, true, true);
    mesh->addSmoothGroup(theSmoothGroup);
    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
}

void ObjLoader::addRawVerticeNormalPart(ObjMesh *mesh, FaceList &faces,
                                        int num) {
    std::vector<Face *>::iterator it = faces.begin();
    // Etape 1 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;
    std::vector<float> glVertexBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);
    glVertexBuffer.reserve(3. * reservedSize);

    int indexVertex = 0;
    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->normals[0]);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->normals[1]);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->normals[2]);

        glVertexBuffer.push_back(v1.vertex.x);
        glVertexBuffer.push_back(v1.vertex.y);
        glVertexBuffer.push_back(v1.vertex.z);
        glVertexBuffer.push_back(v1.normal.x);
        glVertexBuffer.push_back(v1.normal.y);
        glVertexBuffer.push_back(v1.normal.z);

        glVertexBuffer.push_back(v2.vertex.x);
        glVertexBuffer.push_back(v2.vertex.y);
        glVertexBuffer.push_back(v2.vertex.z);
        glVertexBuffer.push_back(v2.normal.x);
        glVertexBuffer.push_back(v2.normal.y);
        glVertexBuffer.push_back(v2.normal.z);

        glVertexBuffer.push_back(v3.vertex.x);
        glVertexBuffer.push_back(v3.vertex.y);
        glVertexBuffer.push_back(v3.vertex.z);
        glVertexBuffer.push_back(v3.normal.x);
        glVertexBuffer.push_back(v3.normal.y);
        glVertexBuffer.push_back(v3.normal.z);

        int index1 = indexVertex++;
        int index2 = indexVertex++;
        int index3 = indexVertex++;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->normals[3]);
            glVertexBuffer.push_back(v4.vertex.x);
            glVertexBuffer.push_back(v4.vertex.y);
            glVertexBuffer.push_back(v4.vertex.z);
            glVertexBuffer.push_back(v4.normal.x);
            glVertexBuffer.push_back(v4.normal.y);
            glVertexBuffer.push_back(v4.normal.z);

            int index4 = indexVertex++;

            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());
    // etape 2 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, true, false);
    mesh->addSmoothGroup(theSmoothGroup);
    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
}

void ObjLoader::addRawVerticeTexturePart(ObjMesh *mesh, FaceList &faces,
                                         int num) {
    std::vector<Face *>::iterator it = faces.begin();
    // Etape 1 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;
    std::vector<float> glVertexBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);
    glVertexBuffer.reserve(3. * reservedSize);

    int indexVertex = 0;
    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0], (*it)->textures[0], true);
        VertexObj v2(*this, (*it)->vertices[1], (*it)->textures[1], true);
        VertexObj v3(*this, (*it)->vertices[2], (*it)->textures[2], true);

        glVertexBuffer.push_back(v1.vertex.x);
        glVertexBuffer.push_back(v1.vertex.y);
        glVertexBuffer.push_back(v1.vertex.z);
        glVertexBuffer.push_back(v1.texcoord[0]);
        glVertexBuffer.push_back(v1.texcoord[1]);

        glVertexBuffer.push_back(v2.vertex.x);
        glVertexBuffer.push_back(v2.vertex.y);
        glVertexBuffer.push_back(v2.vertex.z);
        glVertexBuffer.push_back(v2.texcoord[0]);
        glVertexBuffer.push_back(v2.texcoord[1]);

        glVertexBuffer.push_back(v3.vertex.x);
        glVertexBuffer.push_back(v3.vertex.y);
        glVertexBuffer.push_back(v3.vertex.z);
        glVertexBuffer.push_back(v3.texcoord[0]);
        glVertexBuffer.push_back(v3.texcoord[1]);

        int index1 = indexVertex++;
        int index2 = indexVertex++;
        int index3 = indexVertex++;

        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3], (*it)->textures[3], true);
            glVertexBuffer.push_back(v4.vertex.x);
            glVertexBuffer.push_back(v4.vertex.y);
            glVertexBuffer.push_back(v4.vertex.z);
            glVertexBuffer.push_back(v4.texcoord[0]);
            glVertexBuffer.push_back(v4.texcoord[1]);

            int index4 = indexVertex++;

            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());
    // etape 2 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, false, true);
    mesh->addSmoothGroup(theSmoothGroup);
    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
}

void ObjLoader::addRawVerticePart(ObjMesh *mesh, FaceList &faces, int num) {
    std::vector<Face *>::iterator it = faces.begin();
    // Etape 1 : construire la liste des faces
    std::vector<int> triangleBuffer;
    std::vector<int> quadBuffer;
    std::vector<float> glVertexBuffer;

    int reservedSize = faces.size();
    triangleBuffer.reserve(reservedSize);
    quadBuffer.reserve(reservedSize);
    glVertexBuffer.reserve(3. * reservedSize);

    int indexVertex = 0;
    it = faces.begin();
    do {
        VertexObj v1(*this, (*it)->vertices[0]);
        VertexObj v2(*this, (*it)->vertices[1]);
        VertexObj v3(*this, (*it)->vertices[2]);

        glVertexBuffer.push_back(v1.vertex.x);
        glVertexBuffer.push_back(v1.vertex.y);
        glVertexBuffer.push_back(v1.vertex.z);
        glVertexBuffer.push_back(v2.vertex.x);
        glVertexBuffer.push_back(v2.vertex.y);
        glVertexBuffer.push_back(v2.vertex.z);
        glVertexBuffer.push_back(v3.vertex.x);
        glVertexBuffer.push_back(v3.vertex.y);
        glVertexBuffer.push_back(v3.vertex.z);

        int index1 = indexVertex++;
        int index2 = indexVertex++;
        int index3 = indexVertex++;
        if ((*it)->type == TRIANGLE) {
            triangleBuffer.push_back(index1);
            triangleBuffer.push_back(index2);
            triangleBuffer.push_back(index3);
        } else {
            VertexObj v4(*this, (*it)->vertices[3]);

            glVertexBuffer.push_back(v4.vertex.x);
            glVertexBuffer.push_back(v4.vertex.y);
            glVertexBuffer.push_back(v4.vertex.z);

            int index4 = indexVertex++;

            quadBuffer.push_back(index1);
            quadBuffer.push_back(index2);
            quadBuffer.push_back(index3);
            quadBuffer.push_back(index4);
        }
        delete (*it);
    } while (++it != faces.end());
    // etape 2 : construire le Mesh pour le renderer
    SmoothGroup *theSmoothGroup = new SmoothGroup(glVertexBuffer,
                                                  triangleBuffer, quadBuffer, false, false);
    mesh->addSmoothGroup(theSmoothGroup);
    // On nettoie tout !
    glVertexBuffer.erase(glVertexBuffer.begin(), glVertexBuffer.end());
    triangleBuffer.erase(triangleBuffer.begin(), triangleBuffer.end());
    quadBuffer.erase(quadBuffer.begin(), quadBuffer.end());
}

// TODO : Ecrire la transformation des objets en table de sommets/table de triangle
/*
 Pour chaque objet, construire une liste unique de sommets et de triangles, récupérer le matériau et appeler un callback pour mettre l'objet dans la scène.
 */
void ObjLoader::getObjects(std::vector<loaders::Mesh *> &meshes, std::vector<loaders::Material *> &materials) {
    // add materials to the scene
    int meshnbr=0;
    for (std::map<std::string, mtlMaterial *>::iterator it = mtllib.begin();
         it != mtllib.end(); ++it) {
        //copier mtlMaterial dans Material
        mtlMaterial *mat = it->second;
        Material *material = new Material();
        material->name = mat->name;
        for(int i=0;i<3;i++){
            material->Ka[i] = mat->Ka[i];
            material->Kd[i] = mat->Kd[i];
            material->Ks[i] = mat->Ks[i];
            material->Tf[i] = mat->Tf[i];
        }
        material->bumpmap = mat->bumpmap;
        material->bumpmap_scale = mat->bumpmap_scale;
        material->decalmap = mat->decalmap;
        material->dispmap = mat->dispmap;
        material->dissolve = mat->dissolve;
        material->illum = mat->illum;
        material->ior = mat->ior;
        material->map_Ns = mat->map_Ns;
        material->map_Ns_scale = mat->map_Ns_scale;
        material->map_d = mat->map_d;
        material->map_d_scale = mat->map_d_scale;
        material->map_ka = mat->map_ka;
        material->map_ka_scale = mat->map_ka_scale;
        material->map_kd = mat->map_kd;
        material->map_kd_scale = mat->map_kd_scale;
        material->map_ks = mat->map_ks;
        material->map_ks_scale = mat->map_ks_scale;
        material->normalmap = mat->normalmap;
        material->reflmap = mat->reflmap;
        material->sharpness = mat->sharpness;
        material->shininess = mat->shininess;

        //construire un vecteur de Material
        materials.push_back(material);
    }
    //add geometries to the scene
    {
        // add geometries
        for (std::map<std::string, Group *>::iterator group = allgroups.begin();
             group != allgroups.end(); ++group) {
            Group *theGroup = group->second;
            if (!theGroup->empty) {
                meshnbr++;
                ObjMesh *theMesh;
                //std::cerr << "Material name : " << theGroup->getMaterial() << std::endl;
                theMesh = new ObjMesh(theGroup->name, getMaterialByName(theGroup->getMaterial(),materials));
                for (std::map<int, FaceList>::iterator sg =
                     theGroup->faces.begin(); sg != theGroup->faces.end();
                     ++sg) {
                    //std::cerr << "Traitement de " << theGroup->name << " smooth group " << sg->first << std::endl;
                    std::vector<Face *>::iterator it = sg->second.begin();
                    if (it != sg->second.end()) {
                        // le groupe n'est pas vide !
                        int type = faceType(*it);
                        switch (type) {
                        case 0: // group with complete faces
                            if (sg->first == 0) {
                                // pas de lissage sur ce smoothgroup
                                // ajout des sommets sans reorganisation
                                addRawVerticeNormalTexturePart(theMesh,
                                                               sg->second, sg->first);
                            } else {
                                addVerticeNormalTexturePart(theMesh, sg->second,
                                                            sg->first);
                            }
                            break;
                        case 1: // groupe avec faces vertex+normales
                            if (sg->first == 0) {
                                // pas de lissage sur ce smoothgroup
                                // ajout des sommets sans reorganisation
                                addRawVerticeNormalPart(theMesh, sg->second,
                                                        sg->first);
                            } else {
                                addVerticeNormalPart(theMesh, sg->second,
                                                     sg->first);
                            }
                            break;
                        case 2: // groupe avec faces vertex+textures
                            if (sg->first == 0) {
                                // pas de lissage sur ce smoothgroup
                                // ajout des sommets sans reorganisation
                                addRawVerticeTexturePart(theMesh, sg->second,
                                                         sg->first);
                            } else {
                                addVerticeTexturePart(theMesh, sg->second,
                                                      sg->first);
                            }
                            break;
                        case 3: // groupe avec faces vertex uniquement
                            if (sg->first == 0) {
                                // pas de lissage sur ce smoothgroup
                                // ajout des sommets sans reorganisation
                                addRawVerticePart(theMesh, sg->second,
                                                  sg->first);
                            } else {
                                addVerticePart(theMesh, sg->second, sg->first);
                            }
                            break;
                        default:
                            std::cerr << "Cas normalement impossible !"
                                      << std::endl;
                        }
                    }
                }
                //Mesh *tmp = theMesh->compile();
                meshes.push_back(theMesh->compile());
                //std::cerr<<"mesh id material = "<<tmp->getMaterialId()<<std::endl;
                //meshes.push_back(tmp);

                delete theMesh;
            }
            delete theGroup;
        }
    }
}

/**
  * function which return id of the mat in material vector
  */
int ObjLoader::getMaterialByName(const std::string matName,const std::vector<loaders::Material *> &materials){
    //std::cerr<<std::endl<<std::endl<<"nbr de mat ="<<materials.size()<<std::endl;
    //demare a un puisque le 0 correspond a la texture par defaut
    for(int i=1;i<materials.size();i++){
        loaders::Material *mat=materials[i];
        if (matName.compare(mat->name) == 0){
            //std::cerr<<"return index ="<<i<<std::endl;
            return i;
        }
    }
    //si rien n'a ete trouve on met le material par defaut.
    //std::cerr<<"return index ="<<0<<std::endl;
    return 0;
}

} // end namespace obj

} // end namespace loaders
